/*
 *  V4L2 video capture example
 *
 *  This program can be used and distributed without restrictions.
 *
 *      This program is provided with the V4L2 API
 * see https://linuxtv.org/docs.php for more information
 */

#include "bsp_v4l2.h"

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include <getopt.h> /* getopt_long() */

#include <fcntl.h> /* low-level i/o */
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>

#include <linux/videodev2.h>

#include "debug.h"

#define DEFULT_TIMEOUT_S                    8

#define CLEAR(x)        memset(&(x), 0, sizeof(x))
#define this            (*ptData)

// static int fd = -1;                                             // 设备文件描述符
// struct buffer *buffers;                                         // 
// cam_buf_t *buffers;                                             // 
// static unsigned int n_buffers;                                  // 

static const char *szCameraErrStr[] = {     // 与 camera_error_t 强绑定
    "no err",
    "drv not exist",
    "drv dir err",
    "open dev err",
    "sft not exist",
    "dev ope err",
    "video not supt",
    "read io not supt",
    "stream not supt",
    "mmap not supt",
    "out of ram",
    "timeout",
    "sys",
    "unknown err",
};

static const char *szCtrlNames[cam_func_max] = { // 摄像头控制参数名称   (与 camera_func_t 和 aCtrlIdMap 强绑定)
    "auto-focusing",                        // 自动对焦
    "auto-WBT",                             // 自动白平衡 (white_balance_temperature_auto)
    "brightness",                           // 亮度
    "contrast",                             // 对比度
    "saturation",                           // 饱和度
    "hue",                                  // 色度
    "gamma",                                // 伽马值
    "WBT",                                  // 白平衡 (关自动白平衡时可用)
    "sharpness",                            // 锐度
    "focus_absolute",                       // 焦距 (关自动对焦时可用)
    "zoom_absolute",                        // 缩放
};
static const int aCtrlIdMap[cam_func_max] = { // 填写对应的控制参数ID
    V4L2_CID_FOCUS_AUTO,
    V4L2_CID_AUTO_WHITE_BALANCE,
    V4L2_CID_BRIGHTNESS,
    V4L2_CID_CONTRAST,
    V4L2_CID_SATURATION,
    V4L2_CID_HUE,
    V4L2_CID_GAMMA,
    V4L2_CID_WHITE_BALANCE_TEMPERATURE,
    V4L2_CID_SHARPNESS,
    V4L2_CID_FOCUS_ABSOLUTE,
    V4L2_CID_ZOOM_ABSOLUTE,
};

/// @brief 执行ioctl系统调用并处理被信号中断的情况
/// @param fh 文件描述符
/// @param request ioctl请求命令
/// @param arg ioctl参数指针
/// @return 成功返回0，失败返回-1并设置errno
static int xioctl(int fh, int request, void *arg) {
    int r;
    do {
        r = ioctl(fh, request, arg);
    } while (-1 == r && EINTR == errno);
    return r;
}

/// @brief 
/// @param ptData 
/// @param iFrameNum 第几帧
/// @param p 指向图像数据的指针
/// @param size 图像数据的大小（字节）
static void process_image(v4l2_parm_t *ptData, int32_t iCurrFramNum, const void *p, int size) {
    _DEBUG_PRINTF(DBG_SYS, "Frame %2d captured, size: %d(bytes)\r\n", iCurrFramNum, size);
    if (this.sPhotoDir && this.iUseNum == iCurrFramNum) {
        _DEBUG_PRINT(DBG_SYS, "saving...\n");
        FILE *ptOutFile;
        ptOutFile = fopen(this.sPhotoDir, "wb");
        if (!ptOutFile) {
            assert(0);
        }
        fwrite(p, size, 1, ptOutFile);
        fclose(ptOutFile);
        _DEBUG_PRINTF(DBG_SYS, "save as: %s\n", this.sPhotoDir);
    }
}

/// @brief 
/// @param ptData 
/// @param iCurrFramNum 
/// @return 
static int read_frame(v4l2_parm_t *ptData, int32_t iCurrFramNum) {
    switch (this.eIoMethod)
    {
    case IO_METHOD_READ: {
        #if 0
        if (-1 == read(fd, buffers[0].start, buffers[0].length))
        {
            switch (errno)
            {
            case EAGAIN:
                return 0;

            case EIO:
                /* Could ignore EIO, see spec. */

                /* fall through */

            default:
                errno_exit("read");
            }
        }

        process_image(buffers[0].start, buffers[0].length);
        #endif
        break;
    }
    case IO_METHOD_MMAP: {
        struct v4l2_buffer buf;
        CLEAR(buf);
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        if (xioctl(this.iCameraId, VIDIOC_DQBUF, &buf) == -1) {
            switch (errno)
            {
            case EAGAIN:
                return 0;
            case EIO:
                /* Could ignore EIO, see spec. */
                /* fall through */
            default:
                // errno_exit("VIDIOC_DQBUF");
                assert(0);
            }
        }
        assert(buf.index < this.uBufNum);
        process_image(ptData, iCurrFramNum, this.pBufs[buf.index].start, buf.bytesused);
        if (xioctl(this.iCameraId, VIDIOC_QBUF, &buf) == -1) {
            // errno_exit("VIDIOC_QBUF");
            assert(0);
        }
        break;
    }
    case IO_METHOD_USERPTR:{
        #if 0
        struct v4l2_buffer buf;
        unsigned int i;
        CLEAR(buf);

        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_USERPTR;

        if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf))
        {
            switch (errno)
            {
            case EAGAIN:
                return 0;

            case EIO:
                /* Could ignore EIO, see spec. */

                /* fall through */

            default:
                errno_exit("VIDIOC_DQBUF");
            }
        }

        for (i = 0; i < n_buffers; ++i)
            if (buf.m.userptr == (unsigned long)buffers[i].start && buf.length == buffers[i].length)
                break;

        assert(i < n_buffers);

        process_image((void *)buf.m.userptr, buf.bytesused);

        if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
            errno_exit("VIDIOC_QBUF");
        #endif
        break;
    }
    }
    return 1;
}

/// @brief 
/// @param ptData 
/// @return 错误码
static camera_error_t init_read(v4l2_parm_t *ptData, unsigned int buffer_size) {
#if 0
    buffers = calloc(1, sizeof(*buffers));

    if (!buffers)
    {
        fprintf(stderr, "Out of memory\n");
        exit(EXIT_FAILURE);
    }

    buffers[0].length = buffer_size;
    buffers[0].start = malloc(buffer_size);

    if (!buffers[0].start)
    {
        fprintf(stderr, "Out of memory\n");
        exit(EXIT_FAILURE);
    }
#endif
    return err_none;
}

/// @brief 
/// @param ptData 
/// @return 错误码
static camera_error_t init_mmap(v4l2_parm_t *ptData) {
    struct v4l2_requestbuffers req;
    CLEAR(req);
    req.count = 1;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;
    if (xioctl(this.iCameraId, VIDIOC_REQBUFS, &req) == -1) {   // 请求缓冲区
        if (EINVAL == errno) {                                  // 错误处理
            return err_mmap;
        } else {
            _DEBUG_PRINT(DBG_ERR, "err\n");
            return err_dev_ope;
        }
    }
    if (req.count < 1) {                                        // 错误处理
        return err_out_of_ram;
    }
    this.pBufs = calloc(req.count, sizeof(*(this.pBufs)));      // 申请内存
    if (!this.pBufs) {                                          // 错误处理
        return err_out_of_ram;
    }

    // 查询并映射每个缓冲区
    for (this.uBufNum = 0; this.uBufNum < req.count; ++(this.uBufNum)) {
        struct v4l2_buffer buf;
        CLEAR(buf);
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = this.uBufNum;
        // 使用 VIDIOC_QUERYBUF 查询缓冲区的详细信息
        if (xioctl(this.iCameraId, VIDIOC_QUERYBUF, &buf) == -1) {
            _DEBUG_PRINT(DBG_ERR, "err\n");
            return err_dev_ope;
        }
        this.pBufs[this.uBufNum].length = buf.length;           // 获取缓冲区长度
        this.pBufs[this.uBufNum].start =                        // 使用 mmap() 系统调用将内核缓冲区内存映射到用户空间
            mmap(NULL /* start anywhere */,
                buf.length,
                PROT_READ | PROT_WRITE /* required */,
                MAP_SHARED /* recommended */,
                this.iCameraId, buf.m.offset);
        if (this.pBufs[this.uBufNum].start == MAP_FAILED) {     // 错误处理
            return err_mmap;
        }
    }
    return err_none;
}

/// @brief 
/// @param ptData 
/// @return 错误码
static camera_error_t init_userp(v4l2_parm_t *ptData, unsigned int buffer_size) {
    #if 0
    struct v4l2_requestbuffers req;

    CLEAR(req);

    req.count = 4;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_USERPTR;

    if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req))
    {
        if (EINVAL == errno)
        {
            fprintf(stderr, "%s does not support "
                            "user pointer i/o\n",
                    this.sDevName);
            exit(EXIT_FAILURE);
        }
        else
        {
            errno_exit("VIDIOC_REQBUFS");
        }
    }

    buffers = calloc(4, sizeof(*buffers));

    if (!buffers)
    {
        fprintf(stderr, "Out of memory\n");
        exit(EXIT_FAILURE);
    }

    for (n_buffers = 0; n_buffers < 4; ++n_buffers)
    {
        buffers[n_buffers].length = buffer_size;
        buffers[n_buffers].start = malloc(buffer_size);

        if (!buffers[n_buffers].start)
        {
            fprintf(stderr, "Out of memory\n");
            exit(EXIT_FAILURE);
        }
    }
    #endif
    return err_none;
}

/// ------------------------------------------------------------------------------------------------
/// ------------------------------------------------------------------------------------------------
/// ------------------------------------------------------------------------------------------------

/// @brief 循环等待目标帧
/// @param ptData 
/// @return 错误码
camera_error_t bsp_cam_mainloop(v4l2_parm_t *ptData) {
    uint32_t uCnt = this.iUseNum;
    uint32_t uEffectiveFrame = 0;
    while (uCnt-- > 0) {
        while (1) {
            fd_set fds;                     // 存储文件描述符集合
            struct timeval tv;              // 
            FD_ZERO(&fds);                  // 初始化文件描述符集合为空
            FD_SET(this.iCameraId, &fds);   // 将摄像头设备的文件描述符添加到集合中
            // Timeout.
            // 超时.
            tv.tv_sec = DEFULT_TIMEOUT_S;   // 设置超时时间
            tv.tv_usec = 0;
            int r = select(this.iCameraId + 1, &fds, NULL, NULL, &tv);// 等待设备文件变为可读状态
            if (r == -1) {
                if (EINTR == errno) continue;// 如果是系统中断信号（EINTR），则继续循环等待
                return err_sys;
            }
            if (r == 0) {                   // 处理超时
                return err_timeout;
            }
            if (read_frame(ptData, ++uEffectiveFrame)) {
                break;
            }
            // EAGAIN - continue select loop.
            // EAGAIN - 继续选择循环。
            // 如果读取成功，read_frame() 返回1，跳出内层循环
            // 如果读取失败（通常表示需要重试），继续内层循环等待
        }
    }
}

/// @brief 停止采集
/// @param ptData 
/// @return 错误码
camera_error_t bsp_cam_stop_capturing(v4l2_parm_t *ptData) {
    switch (this.eIoMethod) {
    case IO_METHOD_READ:
        /* Nothing to do. */
        break;
    case IO_METHOD_MMAP:
    case IO_METHOD_USERPTR: {
        enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        if (xioctl(this.iCameraId, VIDIOC_STREAMOFF, &type) == -1) {
            _DEBUG_PRINT(DBG_ERR, "err\n");
            return err_dev_ope;
        }
        break;
    }
    }
}

/// @brief 开始采集
/// @param ptData 
/// @return 错误码
camera_error_t bsp_cam_start_capturing(v4l2_parm_t *ptData) {
    switch (this.eIoMethod) {
    case IO_METHOD_READ:{
        /* Nothing to do. */
        break;
    }
    case IO_METHOD_MMAP:{
        for (uint32_t i = 0; i < this.uBufNum; ++i) {           // 循环遍历所有缓冲区
            struct v4l2_buffer buf;
            CLEAR(buf);
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;             // 缓冲区类型为视频捕获
            buf.memory = V4L2_MEMORY_MMAP;                      // 缓冲区类型为内存映射
            buf.index = i;                                      // 缓冲区索引为当前循环变量i
            if (xioctl(this.iCameraId, VIDIOC_QBUF, &buf) == -1) {// 将缓冲区加入队列
                _DEBUG_PRINT(DBG_ERR, "err\n");
                return err_dev_ope;
            }
        }
        enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  // 缓冲区类型为视频捕获
        if (xioctl(this.iCameraId, VIDIOC_STREAMON, &type) == -1) {// 启动视频流
            _DEBUG_PRINT(DBG_ERR, "err\n");
            return err_dev_ope;
        }
        break;
    }
    case IO_METHOD_USERPTR:{
        #if 0
        for (uint32_t i = 0; i < this.uBufNum; ++i) {
            struct v4l2_buffer buf;

            CLEAR(buf);
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buf.memory = V4L2_MEMORY_USERPTR;
            buf.index = i;
            buf.m.userptr = (unsigned long)buffers[i].start;
            buf.length = buffers[i].length;

            if (xioctl(this.iCameraId, VIDIOC_QBUF, &buf) == -1)
                errno_exit("VIDIOC_QBUF");
        }
        enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        if (xioctl(this.iCameraId, VIDIOC_STREAMON, &type) == -1)
            errno_exit("VIDIOC_STREAMON");
        #endif
        break;
    }
    }
    return err_none;
}

/// @brief 释放设备资源
/// @param ptData 
/// @return 错误码
camera_error_t bsp_cam_uninit_camera(v4l2_parm_t *ptData) {
    switch (this.eIoMethod) {
    case IO_METHOD_READ: {
        free(this.pBufs[0].start);
        break;
    }
    case IO_METHOD_MMAP: {
        for (unsigned int i = 0; i < this.uBufNum; ++i) {
            if (munmap(this.pBufs[i].start, this.pBufs[i].length) == -1) {
                return err_sys;
            }
        }
        break;
    }
    case IO_METHOD_USERPTR: {
        for (unsigned int i = 0; i < this.uBufNum; ++i)
            free(this.pBufs[i].start);
        break;
    }
    }
    free(this.pBufs);
    return err_none;
}

/// @brief 初始化摄像头
/// @param ptData 
/// @return 错误码
camera_error_t bsp_cam_init(v4l2_parm_t *ptData) {
    struct v4l2_capability cap;
    if (xioctl(this.iCameraId, VIDIOC_QUERYCAP, &cap) == -1) {  // 检查V4L2驱动是否存在
        if (EINVAL == errno) {
            return err_sft_not_exist;
        } else {
            _DEBUG_PRINT(DBG_ERR, "err\n");
            return err_dev_ope;
        }
    }
    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {         // 检查设备是否支持视频捕获
        return err_no_video;
    }
    switch (this.eIoMethod) {                                   // 检查I/O方式支持
    case IO_METHOD_READ: {                                      // 读方式
        if (!(cap.capabilities & V4L2_CAP_READWRITE)) {
            return err_no_read_io;
        }
        break;
    }
    case IO_METHOD_MMAP:                                        // MMAP方式
    case IO_METHOD_USERPTR: {                                   // 用户指针方式
        if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
            return err_no_stream;
        }
        break;
    }
    }

    struct v4l2_cropcap cropcap;
    struct v4l2_crop crop;
    CLEAR(cropcap);
    cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;                 // 设置类型为视频捕获
    if (xioctl(this.iCameraId, VIDIOC_CROPCAP, &cropcap) == 0) {// 查询裁剪能力
        crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;                // 将裁剪区域设置为默认矩形区域
        crop.c = cropcap.defrect;                               // 重置为默认值
        // 这实际上是不进行裁剪，使用摄像头推荐的完整画面
        if (xioctl(this.iCameraId, VIDIOC_S_CROP, &crop) == -1) {
            switch (errno) {
            case EINVAL:
                /* Cropping not supported. */
                break;
            default:
                /* Errors ignored. */
                break;
            }
        }
    } else {
        /* Errors ignored. */
    }

    struct v4l2_format fmt;
    CLEAR(fmt);
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;                     // 设置从摄像头读取视频数据
    if (!this.bUseDefault) {
        fmt.fmt.pix.width = this.uWidth;                        // 设置宽
        fmt.fmt.pix.height = this.uHeight;                      // 设置高
        fmt.fmt.pix.pixelformat = this.uPixelFormat;            // 设置格式为MJPEG以支持更高分辨率
        // fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;              // demo (AI:设置为 V4L2_FIELD_INTERLACED 通常是一个安全的默认选择，因为驱动程序会根据实际硬件能力进行调整)
        fmt.fmt.pix.field = V4L2_FIELD_NONE;                    // 逐行扫描-写死拉倒(AI:大多数网络摄像头使用逐行扫描)
        if (xioctl(this.iCameraId, VIDIOC_S_FMT, &fmt) == -1) { // 应用配置
            _DEBUG_PRINT(DBG_ERR, "err\n");
            return err_dev_ope;
        }
        // 注意：VIDIOC_S_FMT可能会改变宽度和高度。
        // Note VIDIOC_S_FMT may change width and height.
    } else {
        // 保留由 v4l2-ctl 设置的原始例程参数设置
        // Preserve original settings as set by v4l2-ctl for example
        if (xioctl(this.iCameraId, VIDIOC_G_FMT, &fmt) == -1) {
            _DEBUG_PRINT(DBG_ERR, "err\n");
            return err_dev_ope;
        }
    }

    // 格式参数修正：确保缓冲区大小参数满足最小要求，防止驱动程序返回不合理的值。
    // 对有缺陷驱动程序的偏执...
    // Buggy driver paranoia.
    unsigned int min = fmt.fmt.pix.width * 2;
    if (fmt.fmt.pix.bytesperline < min)
        fmt.fmt.pix.bytesperline = min;
    min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
    if (fmt.fmt.pix.sizeimage < min)
        fmt.fmt.pix.sizeimage = min;
    // 初始化缓冲区
    camera_error_t tRet = err_none;
    switch (this.eIoMethod) {
    case IO_METHOD_READ:
        tRet = init_read(ptData, fmt.fmt.pix.sizeimage);
        break;
    case IO_METHOD_MMAP:
        tRet = init_mmap(ptData);
        break;
    case IO_METHOD_USERPTR:
        tRet = init_userp(ptData, fmt.fmt.pix.sizeimage);
        break;
    }
    return tRet;
}

/// @brief 关闭设备
/// @param ptData 
/// @return 错误码
camera_error_t bsp_cam_close_camera(v4l2_parm_t *ptData) {
    if (close(this.iCameraId) == -1) {
        // errno_exit("close");
        return err_sys;
    }
    this.iCameraId = -1;
    return err_none;
}

/// @brief 打开相机
/// @param ptData 
/// @return 错误码
camera_error_t bsp_cam_open(v4l2_parm_t *ptData) {
    struct stat st;
    if (stat(this.sDevName, &st)) {         // 检查设备是否存在
        return err_dev_not_exist;
    }
    if (!S_ISCHR(st.st_mode)) {             // 检查设备是否为字符设备
        return err_dev_dir;
    }
    this.iCameraId = open(this.sDevName, O_RDWR /* required */ | O_NONBLOCK, 0); // 打开设备
    if (this.iCameraId == -1) {             // 打开失败
        return err_open_dev;
    }
    return err_none;
}

/// @brief 查询设备控制参数当前值
/// @param ptData 
/// @param tCamFunc 
/// @param iVal 
/// @return 错误码
camera_error_t bsp_cam_get_detailed_parm(v4l2_parm_t *ptData, camera_func_t tCamFunc) {
    struct v4l2_queryctrl tQuery;
    CLEAR(tQuery);
    tQuery.id = aCtrlIdMap[tCamFunc];
    if (ioctl(this.iCameraId, VIDIOC_QUERYCTRL, &tQuery) == 0) {
        _DEBUG_PRINTF(DBG_SYS, "%-20s: %5d~%5d default:%d\n", 
            szCtrlNames[tCamFunc],
            tQuery.minimum, tQuery.maximum,
            tQuery.default_value);
    } else {
        _DEBUG_PRINTF(DBG_SYS, "%-20s: nonsupport\n", szCtrlNames[tCamFunc]);
        return err_none;
    }
    return err_none;
}

/// @brief 获取已封装的控制参数的当前值
/// @param ptData 
/// @param tCamFunc 
/// @param iVal 
/// @return 错误码
camera_error_t bsp_cam_get_cur_val(v4l2_parm_t *ptData, camera_func_t tCamFunc, int32_t *iVal) {
    struct v4l2_control tCtrl;
    CLEAR(tCtrl);
    tCtrl.id = aCtrlIdMap[tCamFunc];
    if (ioctl(this.iCameraId, VIDIOC_G_CTRL, &tCtrl) == -1) {
        _DEBUG_PRINTF(DBG_ERR, "get %-20s fail!!!\n", szCtrlNames[tCamFunc]);
        return err_dev_ope;
    }
    _DEBUG_PRINTF(DBG_STP, "get %-20s val: %d\n", szCtrlNames[tCamFunc], tCtrl.value);
    if (iVal) *iVal = tCtrl.value;
    return err_none;
}

/// @brief 获取所有已封装的控制参数的当前值
/// @param ptData 
/// @return 
camera_error_t bsp_cam_get_all_cur_val(v4l2_parm_t *ptData) {
    _DEBUG_PRINTF(DBG_SYS, "some ctrl parm:\n");
    for (size_t i = 0; i < cam_func_max; i++) {
        bsp_camera_get_cur_val(ptData, i, NULL);
    }
    return err_none;
}

/// @brief 获取所有详细控制参数
/// @param ptData 
/// @return 错误码
camera_error_t bsp_cam_get_all_parm_and_val(v4l2_parm_t *ptData) {
    _DEBUG_PRINTF(DBG_SYS, "ctrl parm:\n");
    for (size_t i = 0; i < cam_func_max; i++) {
        if (bsp_camera_get_detailed_parm(ptData, i) != err_none) {
            continue;
        }
    }
    return err_none;
}

/// @brief 设置设备控制参数
/// @param ptData 
/// @param tCamFunc 
/// @param iVal 
/// @return 错误码
camera_error_t bsp_cam_set_parm(v4l2_parm_t *ptData, camera_func_t tCamFunc, int32_t iVal) {
    struct v4l2_control tCtrl;
    CLEAR(tCtrl);
    tCtrl.id = aCtrlIdMap[tCamFunc];
    tCtrl.value = iVal;
    if (ioctl(this.iCameraId, VIDIOC_S_CTRL, &tCtrl) == -1) {
        _DEBUG_PRINTF(DBG_ERR, "set %20s fail!!!\n", szCtrlNames[tCamFunc]);
        return err_dev_ope;
    }
    _DEBUG_PRINTF(DBG_STP, "set %20s %3d success\n", szCtrlNames[tCamFunc], iVal);
    return err_none;
}

/// @brief 设置所有已封装的控制参数的当前值
/// @param ptData 
/// @return 
camera_error_t bsp_cam_set_all_val(v4l2_parm_t *ptData) {
    assert(0);                              // 不用
    _DEBUG_PRINTF(DBG_SYS, "set ctrl parm:\n");
    for (size_t i = 0; i < cam_func_max; i++) {
        bsp_camera_set_parm(ptData, i, 0);
    }
    return err_none;
}

/// @brief 获取错误代码字符串
/// @param err_code 错误代码
/// @return 错误代码对应的字符串
const char *bsp_cam_get_err_str(camera_error_t err_code) {
    if (err_code <= 0) {
        err_code = -err_code;
        return szCameraErrStr[err_code];
    }
    return szCameraErrStr[0];
}



#if 0   // 
#define USE_FRAME_NUM                       60

const char *mid_get_cam_err_str(int32_t err_code) {
    return bsp_cam_get_err_str(err_code);
}

/// @brief 获取照片
/// @param tData 配置参数
/// @return <0-fail, >=0-success
int32_t mid_cam_capture_photo(camera_set_t tData) {
    camera_error_t tRet  = err_none;
    camera_error_t tRet2 = err_none;
    v4l2_parm_t g_V4l2Parm = {
        .sDevName = "/dev/video0",
        .sPhotoDir = OUTPUT_FILE,

        .iCameraId = -1,

        .eIoMethod = IO_METHOD_MMAP,
        .bUseDefault = false,
        .uWidth = 8000,
        .uHeight = 6000,
        .uPixelFormat = V4L2_PIX_FMT_MJPEG,     // 设置格式为MJPEG以支持更高分辨率
        .iUseNum = USE_FRAME_NUM,

        .uBufNum = 0,
        .pBufs = NULL,
    };

    // FILL_LIGHT_ON();                                        // 开补光灯



    // 执行视频采集的主要步骤
    tRet = bsp_camera_open(&g_V4l2Parm);                    // 1. 打开设备
    if (tRet != err_none) goto safe_exit;
    tRet = bsp_camera_init(&g_V4l2Parm);                    // 2. 初始化设备
    if (tRet != err_none) goto safe_exit;

    tRet = bsp_camera_get_all_parm_and_val(&g_V4l2Parm);    // 2.1 获取所有详细控制参数
    if (tRet != err_none) goto safe_exit;
    // tRet = bsp_camera_set_all_val(&g_V4l2Parm);             // 2.2 设置部分支持的控制参数
    // if (tRet != err_none) goto safe_exit;
    tRet = bsp_camera_set_parm(&g_V4l2Parm, cam_func_auto_focus, tData.tAutoFocus);
    if (tRet != err_none) goto safe_exit;
    tRet = bsp_camera_set_parm(&g_V4l2Parm, cam_func_focus_absolute, tData.iFocus);
    if (tRet != err_none) goto safe_exit;
    tRet = bsp_camera_get_all_cur_val(&g_V4l2Parm);         // 2.3 查询部分控制参数的当前值
    if (tRet != err_none) goto safe_exit;
    tRet = bsp_start_capturing(&g_V4l2Parm);                // 3. 开始采集
    if (tRet != err_none) goto safe_exit;
    tRet = bsp_mainloop(&g_V4l2Parm);                       // 4. 主循环捕获指定帧数
    if (tRet != err_none) goto safe_exit;
    tRet2 = bsp_stop_capturing(&g_V4l2Parm);                // 5. 停止采集
    if (tRet != err_none) goto safe_exit;

    safe_exit:
    tRet2 = bsp_uninit_camera(&g_V4l2Parm);                 // 6. 释放设备资源
    tRet2 = bsp_close_camera(&g_V4l2Parm);                  // 7. 关闭设备
    if (tRet != err_none || tRet2 != err_none) goto err_return;



    // FILL_LIGHT_OFF();                                       // 关补光灯
    return err_none;
    err_return:
    // FILL_LIGHT_OFF();                                       // 关补光灯
    _DEBUG_PRINTF(DBG_ERR, "ErrRet:%s-%s\r\n", 
        mid_get_cam_err_str(tRet),
        mid_get_cam_err_str(tRet2));
    return tRet;
}
#endif  // 
